﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.IO;
using System.Drawing;

using SpotItComponents;

using Vitruvian.Distribution;
using Vitruvian.Distribution.SyncPatterns;
using Vitruvian.Serialization;
using Vitruvian.Services;

using Vitruvian.Logging;
using Vitruvian.Testing;

namespace SpotItServerAPI
{
    [ContainsTests("API.SymbolManager")]
    [DistributionInfo]
    [OptimisticSerialization]
    public class SymbolManager : DistributedManager
    {
        #region Private Data Members
        protected CategoryManager categoryManager = null;
        private string symbolSetsDirectory = "";
        private List<SymbolSet> symbolSets = new List<SymbolSet>();
        #endregion
        
        #region Constructors
        public SymbolManager() : base("Symbol Manager")
        {
            logger = Logger.GetLogger(typeof(SymbolManager));
        }
        #endregion

        #region Public Properties and Methods -- Clients can use these methods

        /// <summary>
        /// SymbolSet Add Method
        /// </summary>
        /// <param name="currentSession">Current user's session, which must be at least for a designer</param>
        /// <param name="newSymbolSet">The symbol set to add</param>
        /// <returns>A result object, where result.Success = true if the operation was success.  If the operation was not successful, then
        /// result.Success = false and result.ErrorMessage contains the reason why it failed.</returns>
        [SyncPattern("RPC")]
        public virtual OperationResult Add(Session currentSession, SymbolSet newSymbolSet)
        {
            OperationMethod mth = new OperationMethod(InternalAdd);
            return DoOperation(currentSession, mth, SecurityLevel.Designer, newSymbolSet);
        }

        /// <summary>
        /// SymbolSet Remove Method
        /// </summary>
        /// <param name="currentSession">Current user's session, which must be at least for a designer</param>
        /// <param name="existingSymbolSet">The symbol set to remove. The symbol set id must match one already known to the server.</param>
        /// <returns>A result object, where result.Success = true if the operation was success.  If the operation was not successful, then
        /// result.Success = false and result.ErrorMessage contains the reason why it failed.</returns>
        [SyncPattern("RPC")]
        public virtual OperationResult Remove(Session currentSession, SymbolSet existingSymbolSet)
        {
            OperationMethod mth = new OperationMethod(InternalRemove);
            return DoOperation(currentSession, mth, SecurityLevel.Designer, existingSymbolSet);
        }

        /// <summary>
        /// SymbolSet Update Method
        /// 
        /// Use this method to update the content of a symbol set, including its categories.  Remember that a symbol set that you get from the server
        /// is just a local copy.  Any changes that you make to it will not be saved to the server unless you update it.
        /// </summary>
        /// <param name="currentSession">Current user's session, which must be at least for a designer</param>
        /// <param name="existingSymbolSet">The symbol set to update.  The symbol set id must match one already known to the server.</param>
        /// <returns>A result object, where result.Success = true if the operation was success.  If the operation was not successful, then
        /// result.Success = false and result.ErrorMessage contains the reason why it failed.</returns>
        [SyncPattern("RPC")]
        public virtual OperationResult Update(Session currentSession, SymbolSet existingSymbolSet)
        {
            OperationMethod mth = new OperationMethod(InternalUpdate);
            return DoOperation(currentSession, mth, SecurityLevel.Designer, existingSymbolSet);
        }

        /// <summary>
        /// Lookup Method
        /// </summary>
        /// <param name="currentSession">Current user's session, which must be at least for a designer</param>
        /// <param name="labelFilter">A string that will act as a filter on the selected symbol sets.  If you pass in a null or empty string, then no
        /// filtering will take place based on the label.  If you pass in a non-empty string, then only filters whose label brings with that non-empty
        /// string will be included in the result set.</param>
        /// <param name="categoryFilter">A category filter. If not null, then only symbol sets in those categories will be returned.
        /// If null, then no filtering take place based on the category</param>
        /// <param name="ownerFilter">A owner filter.  If not null, then only symbol sets belong to that owner will be returned; otherwise no filtering
        /// will be take place based on the owner.</param>
        /// <returns>A result object. If the operation was success, the result.Success = true and result.Data will contain a List<SymbolSet>.
        /// If the operation was not successful, then result.Success = false and result.ErrorMessage contains the reason why it failed.</returns>
        [SyncPattern("RPC")]
        public virtual OperationResult Lookup(Session currentSession, string labelFilter, Category categoryFilter, Guid? ownerFilter)
        {
            OperationMethod mth = new OperationMethod(InternalLookup);
            return DoOperation(currentSession, mth, SecurityLevel.Designer, labelFilter, categoryFilter, ownerFilter);
        }

        /// <summary>
        /// Lookup by Id method
        /// </summary>
        /// <param name="currentSession">Current user's session, which may be at least for a designer</param>
        /// <param name="symbolSetId">The Id of a symbol set to find.</param>
        /// <returns>A result object. If the operation was success, the result.Success = true and result.Data will contain a SymbolSet.
        /// If the operation was not successful, then result.Success = false and result.ErrorMessage contains the reason why it failed.</returns>
        [SyncPattern("RPC")]
        public virtual OperationResult Lookup(Session currentSession, Guid symbolSetId)
        {
            OperationMethod mth = new OperationMethod(InternalLookupById);
            return DoOperation(currentSession, mth, SecurityLevel.Designer, symbolSetId);
        }

        /// <summary>
        /// ChangeOwnership Method
        /// 
        /// An administrator can use this method to change the owner of all symbol set that used to be own by one user to another
        /// </summary>
        /// <param name="currentSession">Current user's session, which must by an administrator</param>
        /// <param name="previousOwner">Id of Previous Owner</param>
        /// <param name="newOwner">Id of the New Owner</param>
        /// <returns>A result object, where result.Success = true if the operation was success.  If the operation was not successful, then
        /// result.Success = false and result.ErrorMessage contains the reason why it failed.</returns>
        [SyncPattern("RPC")]
        public virtual OperationResult ChangeOwnership(Session currentSession, Guid previousOwner, Guid newOwner)
        {
            OperationMethod mth = new OperationMethod(InternalChangeOwnerShip);
            return DoOperation(currentSession, mth, SecurityLevel.Admin, previousOwner, newOwner);
        }

        /// <summary>
        /// RemoveAllSymbolSets methods
        /// 
        /// An administrator can use this method to clear out all existing symbol sets.
        /// </summary>
        /// <param name="currentSession">Current user's session, which must by an administrator</param>
        /// <returns>A result object, where result.Success = true if the operation was success.  If the operation was not successful, then
        /// result.Success = false and result.ErrorMessage contains the reason why it failed.</returns>
        [SyncPattern("RPC")]
        public virtual OperationResult RemoveAllSymbolSets(Session currentSession)
        {
            logger.Debug("In RemoveAllSymbolSets");
            OperationMethod mth = new OperationMethod(InternalRemoveAllSets);
            return DoOperation(currentSession, mth, SecurityLevel.Admin);
        }

        #endregion

        #region Internal Methods for DoOperation
        protected override OperationResult PreCondition()
        {
            OperationResult result = new OperationResult();
            categoryManager = ServiceRegistry.GetPreferredService<CategoryManager>();
            if (categoryManager == null)
                result = new OperationResult("Cannot location a Category Manager -- please restart server process");
            return result;
        }

        private OperationResult InternalAdd(object[] opParams)
        {
            OperationResult result;
            Session currentSession = opParams[0] as Session;
            SymbolSet newSymbolSet = opParams[1] as SymbolSet;
            if (newSymbolSet == null)
                result = new OperationResult("No Symbol Set was specified");
            else if (symbolSets.Contains(newSymbolSet))
                result = new OperationResult("The specified SymbolSet already exists, use the update method to change an existing SymbolSet");
            else
            {
                logger.Debug("The symbol set is not already in known, so add it");

                // Make sure all the categories exist
                foreach (Category c in newSymbolSet.Categories)
                    categoryManager.Add(currentSession, c);

                // Add the new symbol set to the list of known sets
                newSymbolSet.Owner = currentSession.UserInfo.Id;
                symbolSets.Add(newSymbolSet);

                // Save the data to a persistent place
                SaveSymbolSet(newSymbolSet);

                result = new OperationResult();
            }
            return result;
        }

        private OperationResult InternalRemove(object[] opParams)
        {
            OperationResult result;
            SymbolSet existingSymbolSet = opParams[1] as SymbolSet;
            if (existingSymbolSet == null)
                result = new OperationResult("No Symbol Set was specified");
            else if (!symbolSets.Contains(existingSymbolSet))
                result = new OperationResult("The specified SymbolSet does not exist");
            else
            {
                symbolSets.Remove(existingSymbolSet);
                SaveSymbolSet(existingSymbolSet);
                result = new OperationResult();
            }

            return result;
        }

        private OperationResult InternalUpdate(object[] opParams)
        {
            OperationResult result = InternalRemove(opParams);
            if (result.Success)
                result = InternalAdd(opParams);
            return result;
        }

        private OperationResult InternalLookup(object[] opParams)
        {
            OperationResult result = new OperationResult();
            List<SymbolSet> foundList = new List<SymbolSet>();

            Session currentSession = opParams[0] as Session;
            string labelFilter = (string) opParams[1];
            Category categoryFilter = opParams[2] as Category;
            Guid? ownerFilter = (Guid?) opParams[3];

            foreach (SymbolSet ss in symbolSets)
            {
                logger.DebugFormat("Consider symbol: {0}, {1}, {2}", ss.Label, ss.IsPrivate, ss.Owner);
                if ((string.IsNullOrEmpty(labelFilter) ||
                            (ss.Label.Length >= labelFilter.Length && labelFilter.ToLower() == ss.Label.Substring(0, labelFilter.Length).ToLower())) &&
                    (categoryFilter == null || ss.Categories.Contains(categoryFilter)) &&
                    (ownerFilter == null || ownerFilter == ss.Owner) &&
                    (ss.IsPrivate == false || ss.Owner == currentSession.UserInfo.Id))
                {
                    logger.Debug("Add to result");
                    foundList.Add(ss);
                }
            }

            result.Data = foundList;
            return result;
        }

        private OperationResult InternalLookupById(object[] opParams)
        {
            OperationResult result = new OperationResult();

            Session currentSession = opParams[0] as Session;
            Guid symbolSetId = (Guid)opParams[1];
            foreach (SymbolSet ss in symbolSets)
            {
                if (ss.Id == symbolSetId)
                {
                    if (ss.IsPrivate == false || ss.Owner == currentSession.UserInfo.Id)
                        result.Data = ss;
                    break;
                }
            }

            if (result.Data == null)
                result.ErrorMessage = "Cannot find Symbol Set with the specified id";

            return result;
        }

        private OperationResult InternalChangeOwnerShip(object[] opParams)
        {
            OperationResult result = new OperationResult();

            Guid previousOwner = (Guid)opParams[1];
            Guid newOwner = (Guid)opParams[2];

            foreach (SymbolSet ss in symbolSets)
            {
                if (ss.Owner.Equals(previousOwner))
                    ss.Owner = newOwner;
            }

            return result;
        }

        private OperationResult InternalRemoveAllSets(object[] opParams)
        {
            symbolSets.Clear();
            return new OperationResult();
        }

        #endregion

        #region Server-only public methods (not used by client)
        public string SymbolSetsDirectory
        {
            get { return symbolSetsDirectory; }
            set
            {
                symbolSetsDirectory = value;
                if (!string.IsNullOrEmpty(symbolSetsDirectory))
                    if (!Directory.Exists(symbolSetsDirectory))
                        Directory.CreateDirectory(symbolSetsDirectory);
            }
        }

        public bool RemoveCategoryFromAllSymbolSets(Session currentSession, Category category)
        {
            bool result = false;

            sessionManager = ServiceRegistry.GetPreferredService<SessionManager>();
            if (sessionManager != null && category!=null)
            {
                Session validatedSession = sessionManager.Validate(currentSession);

                if (validatedSession != null && (validatedSession.UserInfo.IsAdmin || validatedSession.UserInfo.IsDesigner))
                {
                    logger.DebugFormat("Remove category {0} from all symbol sets", category.Label);
                    lock (myLock)
                    {
                        foreach (SymbolSet ss in symbolSets)
                            ss.RemoveCategory(category);
                    }
                }
            }
            return result;
        }

        #endregion

        #region Save and Load Methods

        protected override void Load()
        {
            lock (myLock)
            {
                // LINQ query for all files containing the word 'Europe'.
                var fileNames = from file in
                                Directory.EnumerateFiles(SymbolSetsDirectory)
                            where file.ToLower().Contains("SymbolSet - ") && file.ToLower().Contains(".xml")
                            select file;

                // Show results.
                foreach (var fileName in fileNames)
                {
                    SymbolSet ss = new SymbolSet();
                    ss.Load(fileName);
                    symbolSets.Add(ss);
                }
            }
        }

        protected override void Save()
        {
            lock (myLock)
            {
                foreach (SymbolSet ss in symbolSets)
                    SaveSymbolSet(ss);
            }
        }

        private void SaveSymbolSet(SymbolSet symbolSet)
        {
            string fileName = "";
            string tmpLabel = "";
            if (!string.IsNullOrEmpty(symbolSet.Label))
            {
                tmpLabel = symbolSet.Label.Replace('/', '-');
                tmpLabel = tmpLabel.Replace('\\', '-');
                tmpLabel = tmpLabel.Replace('.', ' ');
                tmpLabel = tmpLabel.Replace(':', '-');
                tmpLabel += " ";
            }

            fileName = "SymbolSet - " + tmpLabel + "(" + Id.ToString() + ").xml";

            if (!string.IsNullOrEmpty(SymbolSetsDirectory))
                fileName = SymbolSetsDirectory + "/" + fileName;

            symbolSet.Save(fileName);
        }

        #endregion

        #region Reusable Test Data Setup
        public void Test_SetupSampleSymbolSets(Session testSession)
        {
            // First, make sure the necessary categories are set up
            CategoryManager categoryManager = ServiceRegistry.GetPreferredService<CategoryManager>();
            categoryManager.Test_SetupTestCategories(testSession);

            // Create a water-animal symbol set
            SymbolSet ss1 = new SymbolSet("My Favorite Water Animal Symbols", true);
           
            ss1.AddSymbol(Symbol.CreateSymbolWithLabelImage("Alligator", new Bitmap("../../../Symbols/Alligator.jpg")));
            ss1.AddSymbol(Symbol.CreateSymbolWithLabelImage("Crab", new Bitmap("../../../Symbols/Crab.jpg")));
            ss1.AddSymbol(Symbol.CreateSymbolWithLabelImage("Dolphin", new Bitmap("../../../Symbols/Dolphin.jpg")));
            Assert.IsEqual(3, ss1.Symbols.Count);

            List<Category> clist = categoryManager.Lookup(testSession, "Animals", null).Data as List<Category>;
            Assert.IsTrue(0 < clist.Count);
            Assert.IsNotNull(clist[0]);
            ss1.AddCategory(clist[0]);

            // Add the symbol set to the server via the symbol manager, then make sure it is there
            // by getting the new full list of system sets and seeing if the new set is in that list
            OperationResult result = Add(testSession, ss1);
            Assert.IsTrue(result.Success);

            // Create a tree symbol set
            SymbolSet ss2 = new SymbolSet("My Favorite Tree Symbols", true);

            ss2.AddSymbol(Symbol.CreateSymbolWithLabelImage("Bonsi Tree", new Bitmap("../../../Symbols/Tree-1.jpg")));
            ss2.AddSymbol(Symbol.CreateSymbolWithLabelImage("Oak Tree", new Bitmap("../../../Symbols/Tree-2.png")));
            ss2.AddSymbol(Symbol.CreateSymbolWithLabelImage("Apple Tree", new Bitmap("../../../Symbols/Tree-3.gif")));
            ss2.AddSymbol(Symbol.CreateSymbolWithLabelImage("Palm Tree", new Bitmap("../../../Symbols/Tree-4.png")));
            ss2.AddSymbol(Symbol.CreateSymbolWithLabelImage("Flowering Tree", new Bitmap("../../../Symbols/Tree-5.jpg")));
            Assert.IsEqual(5, ss2.Symbols.Count);

            clist = categoryManager.Lookup(testSession, "Trees", null).Data as List<Category>;
            Assert.IsTrue(0 < clist.Count);
            Assert.IsNotNull(clist[0]);
            ss2.AddCategory(clist[0]);

            // Add the symbol set to the server via the symbol manager, then make sure it is there
            // by getting the new full list of system sets and seeing if the new set is in that list
            result = Add(testSession, ss2);
            Assert.IsTrue(result.Success);

            // Create a tree symbol set
            SymbolSet ss3 = new SymbolSet("Flower Symbols", true);

            ss3.AddSymbol(Symbol.CreateSymbolWithLabelImage("Pink Flower", new Bitmap("../../../Symbols/Flower-1.jpg")));
            ss3.AddSymbol(Symbol.CreateSymbolWithLabelImage("Pink and White Flowers", new Bitmap("../../../Symbols/Flower-2.png")));
            ss3.AddSymbol(Symbol.CreateSymbolWithLabelImage("Red Flowers", new Bitmap("../../../Symbols/Flower-3.png")));
            Assert.IsEqual(3, ss3.Symbols.Count);

            clist = categoryManager.Lookup(testSession, "Plants", null).Data as List<Category>;
            Assert.IsTrue(0 < clist.Count);
            Assert.IsNotNull(clist[0]);
            ss2.AddCategory(clist[0]);

            // Add the symbol set to the server via the symbol manager, then make sure it is there
            // by getting the new full list of system sets and seeing if the new set is in that list
            result = Add(testSession, ss3);
            Assert.IsTrue(result.Success);

        }
        #endregion

        #region Unit Test Cases

#if (DEBUG)
        private Session testSession = null;

        [OneTimeSetup]
        private void Test_SymbolManager_Setup()
        {
            sessionManager = ServiceRegistry.GetPreferredService<SessionManager>();
            if (sessionManager == null)
            {
                sessionManager = new SessionManager();
                sessionManager.Init();
                ServiceRegistry.Add(sessionManager);
            }

            categoryManager = ServiceRegistry.GetPreferredService<CategoryManager>();
            if (categoryManager == null)
            {
                categoryManager = new CategoryManager();
                categoryManager.Init();
                ServiceRegistry.Add(categoryManager);
            }

            UserAccount tmpAccount = new UserAccount("tmp", "tmp", "Temporary", "");
            tmpAccount.IsAdmin = true;
            tmpAccount.IsDesigner = true;
            testSession = sessionManager.CreateSession(tmpAccount);
        }


        [Test(1)]
        private void Test_SymbolManager_Constructor_and_RemoveAllSymbolSets()
        {
            SymbolManager symbolManager = new SymbolManager();
            symbolManager.SymbolSetsDirectory = "SymbolSets";
            symbolManager.Init();
            Assert.IsNotNull(symbolManager.symbolSets);

            OperationResult result = symbolManager.RemoveAllSymbolSets(testSession);
            Assert.IsTrue(result.Success);
            Assert.IsNotNull(symbolManager.symbolSets);
            Assert.IsEqual(0, symbolManager.symbolSets.Count);
        }

        [Test(2)]
        private void Test_SymbolManager_Add_And_Lookup()
        {
            SymbolManager symbolManager = new SymbolManager();
            symbolManager.SymbolSetsDirectory = "SymbolSets";
            symbolManager.Init();
            symbolManager.RemoveAllSymbolSets(testSession);

            // Get the current full list of system sets
            OperationResult result = symbolManager.Lookup(testSession, null, null, null);
            Assert.IsTrue(result.Success);
            List<SymbolSet> currentSystemSets = result.Data as List<SymbolSet>;
            Assert.IsNotNull(currentSystemSets);
            Assert.IsEqual(0, currentSystemSets.Count);

            // Create a symbol set locally, and then add it to the server via the symbol manager
            symbolManager.Test_SetupSampleSymbolSets(testSession);

            result = symbolManager.Lookup(testSession, null, null, null);
            Assert.IsTrue(result.Success);
            List<SymbolSet> newSystemSets = result.Data as List<SymbolSet>;
            Assert.IsNotNull(newSystemSets);
            Assert.IsEqual(3, newSystemSets.Count);

            result = symbolManager.Lookup(testSession, "My Favorite", null, null);
            Assert.IsTrue(result.Success);
            newSystemSets = result.Data as List<SymbolSet>;
            Assert.IsNotNull(newSystemSets);
            Assert.IsEqual(2, newSystemSets.Count);

            List<Category> clist = categoryManager.Lookup(testSession, "Trees", null).Data as List<Category>;
            Assert.Equals(1, clist.Count);
            result = symbolManager.Lookup(testSession, null, clist[0], null);
            Assert.IsTrue(result.Success);
            newSystemSets = result.Data as List<SymbolSet>;
            Assert.IsNotNull(newSystemSets);
            Assert.IsEqual(1, newSystemSets.Count);

            result = symbolManager.Lookup(testSession, null, null, testSession.UserInfo.Id);
            Assert.IsTrue(result.Success);
            newSystemSets = result.Data as List<SymbolSet>;
            Assert.IsNotNull(newSystemSets);
            Assert.IsEqual(3, newSystemSets.Count);

            // TODO: finish test cases
        }


#endif
        #endregion

    }
}
